#include "InstancedShader.h"

InstancedShader* InstancedShader::m_pEffect = nullptr;
bool InstancedShader::m_instanceFlag = false;

//---------------------------------------------------------------------------------------------
InstancedShader::InstancedShader()
{ }

//---------------------------------------------------------------------------------------------
InstancedShader::~InstancedShader()
{ }

//---------------------------------------------------------------------------------------------
void InstancedShader::Initialise(ID3D11Device* device)
{
	// Create the shader filename string.
	std::wstring filename = L"character-controller/fx/InstancedBasic.hlsl";

	// Compile the shader file using the ShaderUtil class.
	ShaderUtil::CompileEffect(device, filename, &m_Effect);

	// Link the C++ effect variables to the shader HLSL variables.
	BindVariables();

	// Create the vertex input layout for this shader.
	CreateInputLayout(device);
}

//---------------------------------------------------------------------------------------------
void InstancedShader::Shutdown()
{
	ReleaseCOM(m_Effect);
	ReleaseCOM(m_InputLayout);

	SafeDelete(m_pEffect);
}

//---------------------------------------------------------------------------------------------
InstancedShader* InstancedShader::GetInstance()
{
	// If this class has not yet been instantiated.
	if (m_instanceFlag == false)
	{
		m_pEffect = new InstancedShader;	// Instantiate the shader.
		m_instanceFlag = true;				// Set instance flag to true.
		return m_pEffect;					// Return the instance pointer.
	}
	// If this class has been instantiated.
	else
	{
		return m_pEffect;					// Return the instance pointer.
	}
}

//---------------------------------------------------------------------------------------------
void InstancedShader::BindVariables()
{	
	//==============================
	// Bind Shader Techniques
	//==============================

	Light1Tech					= m_Effect->GetTechniqueByName("Light1");
	Light2Tech					= m_Effect->GetTechniqueByName("Light2");
	Light3Tech					= m_Effect->GetTechniqueByName("Light3");

	Light0TexTech				= m_Effect->GetTechniqueByName("Light0Tex");
	Light1TexTech				= m_Effect->GetTechniqueByName("Light1Tex");
	Light2TexTech				= m_Effect->GetTechniqueByName("Light2Tex");
	Light3TexTech				= m_Effect->GetTechniqueByName("Light3Tex");

	Light0TexAlphaClipTech		= m_Effect->GetTechniqueByName("Light0TexAlphaClip");
	Light1TexAlphaClipTech		= m_Effect->GetTechniqueByName("Light1TexAlphaClip");
	Light2TexAlphaClipTech		= m_Effect->GetTechniqueByName("Light2TexAlphaClip");
	Light3TexAlphaClipTech		= m_Effect->GetTechniqueByName("Light3TexAlphaClip");

	Light1FogTech				= m_Effect->GetTechniqueByName("Light1Fog");
	Light2FogTech				= m_Effect->GetTechniqueByName("Light2Fog");
	Light3FogTech				= m_Effect->GetTechniqueByName("Light3Fog");

	Light0TexFogTech			= m_Effect->GetTechniqueByName("Light0TexFog");
	Light1TexFogTech			= m_Effect->GetTechniqueByName("Light1TexFog");
	Light2TexFogTech			= m_Effect->GetTechniqueByName("Light2TexFog");
	Light3TexFogTech			= m_Effect->GetTechniqueByName("Light3TexFog");

	Light0TexAlphaClipFogTech	= m_Effect->GetTechniqueByName("Light0TexAlphaClipFog");
	Light1TexAlphaClipFogTech	= m_Effect->GetTechniqueByName("Light1TexAlphaClipFog");
	Light2TexAlphaClipFogTech	= m_Effect->GetTechniqueByName("Light2TexAlphaClipFog");
	Light3TexAlphaClipFogTech	= m_Effect->GetTechniqueByName("Light3TexAlphaClipFog");

	//==============================
	// Bind Shader Variables
	//==============================

	WorldViewProj				= m_Effect->GetVariableByName("gWorldViewProj")->AsMatrix();
	World						= m_Effect->GetVariableByName("gWorld")->AsMatrix();
	WorldInvTranspose			= m_Effect->GetVariableByName("gWorldInvTranspose")->AsMatrix();
	ViewProj					= m_Effect->GetVariableByName("gViewProj")->AsMatrix();
	TexTransform				= m_Effect->GetVariableByName("gTexTransform")->AsMatrix();
	EyePosW						= m_Effect->GetVariableByName("gEyePosW")->AsVector();
	FogColor					= m_Effect->GetVariableByName("gFogColor")->AsVector();
	FogStart					= m_Effect->GetVariableByName("gFogStart")->AsScalar();
	FogRange					= m_Effect->GetVariableByName("gFogRange")->AsScalar();
	DirLights					= m_Effect->GetVariableByName("gDirLights");
	Mat							= m_Effect->GetVariableByName("gMaterial");
	DiffuseMap					= m_Effect->GetVariableByName("gDiffuseMap")->AsShaderResource();
}

//-------------------------------------------------------------------
void InstancedShader::CreateInputLayout(ID3D11Device* device)
{
	HRESULT hr;

	// Create input element description that corresponds to the vertex shader signature.
	const D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[8] = 
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
		{"NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
		{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0},
		{ "WORLD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
		{ "WORLD", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
		{ "WORLD", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
		{ "WORLD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 64,  D3D11_INPUT_PER_INSTANCE_DATA, 1 }
	};

	// Get signature of vertex shader.
	D3DX11_PASS_DESC passDesc;
	Light3Tech->GetPassByIndex(0)->GetDesc(&passDesc);

	// Create the input layout.
	hr = device->CreateInputLayout(inputLayoutDesc, 8, passDesc.pIAInputSignature,
		passDesc.IAInputSignatureSize, &m_InputLayout);

	// Output error message if the input layout creation failed.
	if (FAILED(hr))
	{
		MessageBox(NULL, L"InstancedShader::InputLayout Error!", L"Error", MB_OK);
	}
}

//---------------------------------------------------------------------------------------------
ID3D11InputLayout* InstancedShader::GetInputLayout() const
{
	return m_InputLayout;
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetWorldViewProj(CXMMATRIX M)
{
	WorldViewProj->SetMatrix(reinterpret_cast<const float*>(&M));
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetTexTransform(CXMMATRIX M)
{
	TexTransform->SetMatrix(reinterpret_cast<const float*>(&M));
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetEyePosW(const XMFLOAT3& v)
{
	EyePosW->SetRawValue(&v, 0, sizeof(XMFLOAT3));
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetFogColor(const XMVECTOR v)
{
	FogColor->SetFloatVector(reinterpret_cast<const float*>(&v));
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetFogStart(float f)
{
	FogStart->SetFloat(f);
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetFogRange(float f)
{
	FogRange->SetFloat(f);
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetDirLights(const DirectionalLight* lights)
{
	DirLights->SetRawValue(lights, 0, 3*sizeof(DirectionalLight));
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetMaterial(const Material& mat)
{
	Mat->SetRawValue(&mat, 0, sizeof(Material));
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetDiffuseMap(ID3D11ShaderResourceView* tex)
{
	DiffuseMap->SetResource(tex);
}

//---------------------------------------------------------------------------------------------
void InstancedShader::SetViewProj(CXMMATRIX M)
{
	ViewProj->SetMatrix(reinterpret_cast<const float*>(&M));
}